﻿// Copyright (c) Microsoft Corporation.  All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.IO;
using Evaluant.NLinq.Expressions;

namespace Evaluant.Uss.CommonVisitors
{
    /// <summary>
    /// Writes out an expression tree in a C#-ish syntax
    /// </summary>
    public class ExpressionWriter<T> : ExpressionVisitor<T>
        where T : ExpressionUpdater
    {
        TextWriter writer;
        int indent = 2;
        int depth;

        protected ExpressionWriter(TextWriter writer)
            : base(null)
        {
            this.writer = writer;
        }

        public static void Write(TextWriter writer, Expression expression)
        {
            new ExpressionWriter<T>(writer).Visit(expression);
        }

        public static string WriteToString(Expression expression)
        {
            StringWriter sw = new StringWriter();
            Write(sw, expression);
            return sw.ToString();
        }

        protected enum Indentation
        {
            Same,
            Inner,
            Outer
        }

        protected int IndentationWidth
        {
            get { return this.indent; }
            set { this.indent = value; }
        }

        protected void WriteLine(Indentation style)
        {
            this.writer.WriteLine();
            this.Indent(style);
            for (int i = 0, n = this.depth * this.indent; i < n; i++)
            {
                this.writer.Write(" ");
            }
        }

        private static readonly char[] splitters = Environment.NewLine.ToCharArray();
        protected void Write(string text)
        {
            if (text.IndexOf(Environment.NewLine) >= 0)
            {
                string[] lines = text.Split(splitters, StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0, n = lines.Length; i < n; i++)
                {
                    this.Write(lines[i]);
                    if (i < n - 1)
                    {
                        this.WriteLine(Indentation.Same);
                    }
                }
            }
            else
            {
                this.writer.Write(text);
            }
        }

        protected void Indent(Indentation style)
        {
            if (style == Indentation.Inner)
            {
                this.depth++;
            }
            else if (style == Indentation.Outer)
            {
                this.depth--;
                System.Diagnostics.Debug.Assert(this.depth >= 0);
            }
        }

        protected virtual string GetOperator(BinaryExpressionType type)
        {
            //switch (type)
            //{
            //    case ExpressionType.Not:
            //        return "!";
            //    case ExpressionType.Add:
            //    case ExpressionType.AddChecked:
            //        return "+";
            //    case ExpressionType.Negate:
            //    case ExpressionType.NegateChecked:
            //    case ExpressionType.Subtract:
            //    case ExpressionType.SubtractChecked:
            //        return "-";
            //    case ExpressionType.Multiply:
            //    case ExpressionType.MultiplyChecked:
            //        return "*";
            //    case ExpressionType.Divide:
            //        return "/";
            //    case ExpressionType.Modulo:
            //        return "%";
            //    case ExpressionType.And:
            //        return "&";
            //    case ExpressionType.AndAlso:
            //        return "&&";
            //    case ExpressionType.Or:
            //        return "|";
            //    case ExpressionType.OrElse:
            //        return "||";
            //    case ExpressionType.LessThan:
            //        return "<";
            //    case ExpressionType.LessThanOrEqual:
            //        return "<=";
            //    case ExpressionType.GreaterThan:
            //        return ">";
            //    case ExpressionType.GreaterThanOrEqual:
            //        return ">=";
            //    case ExpressionType.Equal:
            //        return "==";
            //    case ExpressionType.NotEqual:
            //        return "!=";
            //    case ExpressionType.Coalesce:
            //        return "??";
            //    case ExpressionType.RightShift:
            //        return ">>";
            //    case ExpressionType.LeftShift:
            //        return "<<";
            //    case ExpressionType.ExclusiveOr:
            //        return "^";
            //    default:
            //        return null;
            //}
            switch (type)
            {
                case BinaryExpressionType.And:
                    return "&&";
                case BinaryExpressionType.Or:
                    return "||";
                case BinaryExpressionType.NotEqual:
                    return "!=";
                case BinaryExpressionType.LesserOrEqual:
                    return "<=";
                case BinaryExpressionType.GreaterOrEqual:
                    return ">=";
                case BinaryExpressionType.Lesser:
                    return "<";
                case BinaryExpressionType.Greater:
                    return ">";
                case BinaryExpressionType.Equal:
                    return "==";
                case BinaryExpressionType.Minus:
                    return "-";
                case BinaryExpressionType.Plus:
                    return "+";
                case BinaryExpressionType.Modulo:
                    return "%";
                case BinaryExpressionType.Div:
                    return "/";
                case BinaryExpressionType.Times:
                    return "*";
                case BinaryExpressionType.Pow:
                    return "^";
                case BinaryExpressionType.Unknown:
                default:
                    return null;
            }
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            Visit(b.LeftExpression);
            Visit(b.Type);
            Visit(b.RightExpression);
            //switch (b.Type)
            //{
            //    case ExpressionType.ArrayIndex:
            //        this.Visit(b.Left);
            //        this.Write("[");
            //        this.Visit(b.Right);
            //        this.Write("]");
            //        break;
            //    case ExpressionType.Power:
            //        this.Write("POW(");
            //        this.Visit(b.Left);
            //        this.Write(", ");
            //        this.Visit(b.Right);
            //        this.Write(")");
            //        break;
            //    default:
            //        this.Visit(b.Left);
            //        this.Write(" ");
            //        this.Write(GetOperator(b.NodeType));
            //        this.Write(" ");
            //        this.Visit(b.Right);
            //        break;
            //}
            return b;
        }

        private void Visit(BinaryExpressionType binaryExpressionType)
        {
            switch (binaryExpressionType)
            {
                case BinaryExpressionType.And:
                    Write(" and ");
                    break;
                case BinaryExpressionType.Or:
                    Write(" or ");
                    break;
                case BinaryExpressionType.NotEqual:
                    Write(" != ");
                    break;
                case BinaryExpressionType.LesserOrEqual:
                    Write(" <= ");
                    break;
                case BinaryExpressionType.GreaterOrEqual:
                    Write(" >= ");
                    break;
                case BinaryExpressionType.Lesser:
                    Write(" < ");
                    break;
                case BinaryExpressionType.Greater:
                    Write(" > ");
                    break;
                case BinaryExpressionType.Equal:
                    Write(" == ");
                    break;
                case BinaryExpressionType.Minus:
                    Write(" - ");
                    break;
                case BinaryExpressionType.Plus:
                    Write(" + ");
                    break;
                case BinaryExpressionType.Modulo:
                    Write(" % ");
                    break;
                case BinaryExpressionType.Div:
                    Write(" / ");
                    break;
                case BinaryExpressionType.Times:
                    Write(" * ");
                    break;
                case BinaryExpressionType.Pow:
                    Write(" ^ ");
                    break;
                case BinaryExpressionType.Unknown:
                    Write(" ??????? ");
                    break;
                default:
                    break;
            }
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.Type)
            {
                case UnaryExpressionType.Not:
                case UnaryExpressionType.Negate:
                    Write("!(");
                    Visit(u.Expression);
                    Write(")");
                    break;
                default:
                    break;
            }
            return u;
            //switch (u.NodeType)
            //{
            //    case ExpressionType.Convert:
            //    case ExpressionType.ConvertChecked:
            //        this.Write("((");
            //        this.Write(this.GetTypeName(u.Type));
            //        this.Write(")");
            //        this.Visit(u.Operand);
            //        this.Write(")");
            //        break;
            //    case ExpressionType.ArrayLength:
            //        this.Visit(u.Operand);
            //        this.Write(".Length");
            //        break;
            //    case ExpressionType.Quote:
            //        this.Visit(u.Operand);
            //        break;
            //    case ExpressionType.TypeAs:
            //        this.Visit(u.Operand);
            //        this.Write(" as ");
            //        this.Write(this.GetTypeName(u.Type));
            //        break;
            //    case ExpressionType.UnaryPlus:
            //        this.Visit(u.Operand);
            //        break;
            //    default:
            //        this.Write(this.GetOperator(u.NodeType));
            //        this.Visit(u.Operand);
            //        break;
            //}
            //return u;
        }

        protected virtual string GetTypeName(Type type)
        {
            string name = type.Name;
            name = name.Replace('+', '.');
            int iGeneneric = name.IndexOf('`');
            if (iGeneneric > 0)
            {
                name = name.Substring(0, iGeneneric);
            }
            if (type.IsGenericType || type.IsGenericTypeDefinition)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(name);
                sb.Append("<");
                var args = type.GetGenericArguments();
                for (int i = 0, n = args.Length; i < n; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(",");
                    }
                    if (type.IsGenericType)
                    {
                        sb.Append(this.GetTypeName(args[i]));
                    }
                }
                sb.Append(">");
                name = sb.ToString();
            }
            return name;
        }

        protected override Expression VisitTernary(TernaryExpression c)
        {
            this.Visit(c.LeftExpression);
            this.WriteLine(Indentation.Inner);
            this.Write("? ");
            this.Visit(c.MiddleExpression);
            this.WriteLine(Indentation.Same);
            this.Write(": ");
            this.Visit(c.RightExpression);
            this.Indent(Indentation.Outer);
            return c;
        }

        //protected override IEnumerable<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> original)
        //{
        //    for (int i = 0, n = original.Count; i < n; i++)
        //    {
        //        this.VisitBinding(original[i]);
        //        if (i < n - 1)
        //        {
        //            this.Write(",");
        //            this.WriteLine(Indentation.Same);
        //        }
        //    }
        //    return original;
        //}

        private static readonly char[] special = new char[] { '\n', '\r', '\\' };

        protected override Expression VisitConstant(ValueExpression c)
        {
            if (c.Value == null)
            {
                this.Write("null");
            }
            else if (c.TypeCode == TypeCode.String)
            {
                string value = c.Value.ToString();
                if (value.IndexOfAny(special) >= 0)
                    this.Write("@");
                this.Write("\"");
                this.Write(c.Value.ToString());
                this.Write("\"");
            }
            else if (c.TypeCode == TypeCode.DateTime)
            {
                this.Write("new DataTime(\"");
                this.Write(c.Value.ToString());
                this.Write("\")");
            }
            //else if (c.Value.GetType().IsArray)
            //{
            //    Type elementType = c.Type.GetElementType();
            //    this.VisitNewArray(
            //        Expression.NewArrayInit(
            //            elementType,
            //            ((IEnumerable)c.Value).OfType<object>().Select(v => (Expression)Expression.Constant(v, elementType))
            //            ));
            //}
            else
            {
                this.Write(c.Value.ToString());
            }
            return c;
        }

        //protected override ElementInit VisitElementInitializer(ElementInit initializer)
        //{
        //    if (initializer.Arguments.Count > 1)
        //    {
        //        this.Write("{");
        //        for (int i = 0, n = initializer.Arguments.Count; i < n; i++)
        //        {
        //            this.Visit(initializer.Arguments[i]);
        //            if (i < n - 1)
        //            {
        //                this.Write(", ");
        //            }
        //        }
        //        this.Write("}");
        //    }
        //    else
        //    {
        //        this.Visit(initializer.Arguments[0]);
        //    }
        //    return initializer;
        //}

        //protected override IEnumerable<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> original)
        //{
        //    for (int i = 0, n = original.Count; i < n; i++)
        //    {
        //        this.VisitElementInitializer(original[i]);
        //        if (i < n - 1)
        //        {
        //            this.Write(",");
        //            this.WriteLine(Indentation.Same);
        //        }
        //    }
        //    return original;
        //}

        protected override Expression[] VisitExpressionList(Expression[] original)
        {
            foreach (Expression exp in original)
            {
                Visit(exp);
                if (exp != original[original.Length - 1])
                {
                    Write(",");
                    WriteLine(Indentation.Same);
                }
            }
            return original;
        }

        //protected override Expression VisitInvocation(InvocationExpression iv)
        //{
        //    this.Write("Invoke(");
        //    this.WriteLine(Indentation.Inner);
        //    this.VisitExpressionList(iv.Arguments);
        //    this.Write(", ");
        //    this.WriteLine(Indentation.Same);
        //    this.Visit(iv.Expression);
        //    this.WriteLine(Indentation.Same);
        //    this.Write(")");
        //    this.Indent(Indentation.Outer);
        //    return iv;
        //}

        //protected override Expression VisitLambda(LambdaExpression lambda)
        //{
        //    if (lambda.Parameters.Count != 1)
        //    {
        //        this.Write("(");
        //        for (int i = 0, n = lambda.Parameters.Count; i < n; i++)
        //        {
        //            this.Write(lambda.Parameters[i].Name);
        //            if (i < n - 1)
        //            {
        //                this.Write(", ");
        //            }
        //        }
        //        this.Write(")");
        //    }
        //    else
        //    {
        //        this.Write(lambda.Parameters[0].Name);
        //    }
        //    this.Write(" => ");
        //    this.Visit(lambda.Body);
        //    return lambda;
        //}

        //protected override Expression VisitListInit(ListInitExpression init)
        //{
        //    this.Visit(init.NewExpression);
        //    this.Write(" {");
        //    this.WriteLine(Indentation.Inner);
        //    this.VisitElementInitializerList(init.Initializers);
        //    this.WriteLine(Indentation.Outer);
        //    this.Write("}");
        //    return init;
        //}

        //protected override Expression VisitMemberAccess(MemberExpression m)
        //{
        //    this.Visit(m.Expression);
        //    this.Write(".");
        //    this.Write(m.Member.Name);
        //    return m;
        //}

        //protected override MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
        //{
        //    this.Write(assignment.Member.Name);
        //    this.Write(" = ");
        //    this.Visit(assignment.Expression);
        //    return assignment;
        //}

        //protected override Expression VisitMemberInit(MemberInitExpression init)
        //{
        //    this.Visit(init.NewExpression);
        //    this.Write(" {");
        //    this.WriteLine(Indentation.Inner);
        //    this.VisitBindingList(init.Bindings);
        //    this.WriteLine(Indentation.Outer);
        //    this.Write("}");
        //    return init;
        //}

        //protected override MemberListBinding VisitMemberListBinding(MemberListBinding binding)
        //{
        //    this.Write(binding.Member.Name);
        //    this.Write(" = {");
        //    this.WriteLine(Indentation.Inner);
        //    this.VisitElementInitializerList(binding.Initializers);
        //    this.WriteLine(Indentation.Outer);
        //    this.Write("}");
        //    return binding;
        //}

        //protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)
        //{
        //    this.Write(binding.Member.Name);
        //    this.Write(" = {");
        //    this.WriteLine(Indentation.Inner);
        //    this.VisitBindingList(binding.Bindings);
        //    this.WriteLine(Indentation.Outer);
        //    this.Write("}");
        //    return binding;
        //}

        protected override Expression VisitMethodCall(MethodCall m)
        {
            if (m.AnonIdentifier != null)
            {
                this.Visit(m.AnonIdentifier);
            }
            else
            {
                this.Write(m.Identifier.Text);
            }
            this.Write(".");
            this.Write(m.Identifier.Text);
            this.Write("(");
            if (m.Parameters.Length > 1)
                this.WriteLine(Indentation.Inner);
            this.VisitExpressionList(m.Parameters);
            if (m.Parameters.Length > 1)
                this.WriteLine(Indentation.Outer);
            this.Write(")");
            return m;
        }

        protected override Expression VisitNew(TypedNew nex)
        {
            this.Write("new ");
            this.Write(nex.Type);
            this.Write("(");
            if (nex.Parameters.Length > 1)
                this.WriteLine(Indentation.Inner);
            this.VisitExpressionList(nex.Parameters);
            if (nex.Parameters.Length > 1)
                this.WriteLine(Indentation.Outer);
            this.Write(")");
            return nex;
        }

        //protected override Expression VisitNewArray(NewArrayExpression na)
        //{
        //    this.Write("new ");
        //    //this.Write(this.GetTypeName(TypeHelper.GetElementType(na.Type)));
        //    this.Write("[] {");
        //    if (na.Expressions.Count > 1)
        //        this.WriteLine(Indentation.Inner);
        //    this.VisitExpressionList(na.Expressions);
        //    if (na.Expressions.Count > 1)
        //        this.WriteLine(Indentation.Outer);
        //    this.Write("}");
        //    return na;
        //}

        //protected override Expression VisitParameter(ParameterExpression p)
        //{
        //    this.Write(p.Name);
        //    return p;
        //}

        //protected override Expression VisitTypeIs(TypeBinaryExpression b)
        //{
        //    this.Visit(b.Expression);
        //    this.Write(" is ");
        //    this.Write(this.GetTypeName(b.TypeOperand));
        //    return b;
        //}

        protected override Expression VisitUnknown(Expression expression)
        {
            this.Write(expression.ToString());
            return expression;
        }
    }
}